
/**
 * 显示一个元素，与S不同的是，修改的是display属性<br>
 * add 20140922
 * 修改visibility的最大缺点是：如果子元素是显示的话，即使父元素隐藏了子元素也不会隐藏
 * @param id
 */
function Show(id)
{
	var temp = G(id);
	if(temp) temp.style.display = 'block';
}
/**
 * 隐藏一个元素，与H不同的是，修改的是display属性<br>
 * add 20140922
 * 修改visibility的最大缺点是：如果子元素是显示的话，即使父元素隐藏了子元素也不会隐藏
 * @param id
 */
function Hide(id)
{
	var temp = G(id);
	if(temp) temp.style.display = 'none';
}

(function(Epg)
{
	
	/**
	 * 通用的tip方法，仅供tip和ykqTip内部调用，未作标清兼容
	 */
	function tip(info, second, id, timerName)
	{
		//info为空时不产生任何效果
		if(info === undefined || info === '') return;
		second = second || 3;
		id = id || id;
		if(!G(id))
		{
			var dom = document.createElement('div');
			dom.id = id;
			document.body.appendChild(dom);
		}
		G(id).innerHTML = info;
		S(id);
		if(second>0)
		{
			//如果上次执行过setTimeout，那么强行停止
			if(Epg[timerName]) clearTimeout(Epg[timerName]);
			Epg[timerName] = setTimeout('H("'+id+'")', second*1000);
		}
	}
	
	Epg.extend = function(params)
	{
		for(var i in params) this[i] = params[i];
	};
	
	Epg.extend(
	{
		/**
		 * 弹出一个通用的提示框，默认3秒消失
		 * @param info 内容
		 * @param second 隐藏的秒数
		 */
		tip: function(info, second)
		{
			tip(info, second, 'default_tip_css', '_tip_timer');
		},
		/**
		 * 弹出一个通用的遥控器提示框，默认3秒消失
		 * @param info 内容
		 * @param second 隐藏的秒数
		 */
		ykqTip: function(info, second)
		{
			tip(info, second, 'default_ykq_tip_css', '_ykq_tip_timer');
		},
		/**
		 * 获取上下文路径，不带最后面的“斜杠”，形如：/health-sd
		 * @returns {String}
		 */
		getBasePath: function()
		{
			var contextPath = '/' + location.href.split('/')[3];
			return contextPath;
		},
		/**
		 * 判断对象是否是数组，add 20140923
		 * @param obj 要判断的对象
		 */
		isArray: function(obj)
		{
			return (obj instanceof Array); 
		},
		/**
		 * 分页方法
		 * @param url 要跳转的url，必须页码必须是最后一个参数，且“=”结尾
		 * @param idx 要跳转的页码
		 * @param pageCount 总页数，只有下一页时才用到
		 */
		page: function(url, idx, pageCount)
		{
			idx = parseInt(idx);
			if(idx < 1)
				Epg.tip('已经是第一页了！');
			else if(pageCount !== undefined && idx > parseInt(pageCount))
				Epg.tip('已经是最后一页了！');
			else
			{
				url += idx;
				if(pageCount === undefined)//如果是上一页（一般上一页时不传此参数，当然这个判断不准确），修改OTT版的移动动画向右
					url += '&transition_animate_dir=right';
				Epg.jump(url);
			}
		},
		/**
		 * 跳转
		 * @param href 要跳转的url
		 * @param f 焦点按钮，默认当前按钮ID
		 * @deprecated 不建议使用本方法
		 */
		jump: function(href,f)
		{
			//if(f === undefined)
			//	f = Epg.btn.current.id;
			//window.location.href = href+'&f='+f;
		},
		/**
		 * 用于开发时控制台输出信息，上线后注释内部代码即可
		 * @param info 
		 */
		debug: function(info)
		{
			//if(debug_mode && typeof console !== 'undefined' && console.log)
			//	console.log(info);
		},
		/**
		 * 机顶盒不支持trim方法，故手动写一个
		 * add 20140606
		 */
		trim: function(str)
		{
			if(str) return str.replace(/^\s*(.*?)\s*$/g, '$1');
		},
		/**
		 * 字符串转驼峰形式
		 * 示例一：$.toHump('get_param')，返回getParam
		 * 示例二：$.toHump('font-size','-')，返回fontSize
		 * @param str
		 * @param 分割的标志，默认为“_”
		 */
		toHump: function(str, flag)
		{
			return str.replace(new RegExp(flag+'(\\w)', 'g'), function(m, $1, idx, str){ return $1.toUpperCase(); });
		},
		/**
		 * 判断对象是否具有某个class，仅保证高清盒子支持，标清盒子请勿使用！add 20150414
		 */
		hasClass: function(obj, cls)
		{
			if(!obj) return;
			return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));  
		},
		/**
		 * 给dom对象添加class，仅保证高清盒子支持，标清盒子请勿使用！add 20150414
		 */
		addClass: function(obj, cls)
		{
			if(!obj) return;
			var className = obj.className;
			if (!this.hasClass(obj, cls)) obj.className += (className?' ':'') + cls;  
		},
		/**
		 * 给对象删除class，仅保证高清盒子支持，标清盒子请勿使用！add 20150414
		 */
		removeClass: function(obj, cls)
		{
			if (obj && this.hasClass(obj, cls))
			{
				obj.className = obj.className.replace(new RegExp('(\\s|^)' + cls + '(\\s|$)'), function(m, $1, $2)
				{
					return ($1&&$2) ? ' ':'';}
				);
			}
		},
		/**
		 * 播放按键声音，目前仅安卓支持，add 20150506
		 * @param sound 按键声音名字，不知道传什么可以传default，所有可选的声音请参看对应的安卓项目
		 */
		playKeySound: function(sound)
		{
			//enable_key_sound这个参数要么没定义，如果定义了，必须是true，如果是false就不播放按键声音，add 20150717
			if(is_ott && sound && ( typeof enable_key_sound === 'undefined' || (typeof enable_key_sound === 'boolean' && enable_key_sound) ))
			{
				//可以在这里设置默认的按键声音
				sound = (sound==='default') ? 'defaultClick' : sound;
				sound = (sound==='defaultClick') ? 'type.wav' : sound;//默认click
				sound = (sound==='defaultMove') ? 'shake.wav' : sound;//默认move
				try{android.playKeySound(sound);}catch(e){}
			}
		},
		/**
		 * 触发按键事件，兼容IE、谷歌、火狐等常见浏览器，尚未在所有标清盒子测试，故仅限高清盒子调用，add 20150515
		 * @param obj 事件对象，一般传document
		 * @param eventType 事件类型，支持 keydown和keypress
		 * @param keyCode 要触发的按键值，数字类型
		 */
		fireKeyEvent: function(obj, eventType, keyCode)
		{
			obj = obj || document;//obj可以不传，update 20150811
			//针对最近发现的部分盒子不支持Epg.fireKeyEvent()方法，现在改为主动调用Epg.eventHandler，update 20150609
			if(typeof keyCode === 'string' && /^EVENT_.*$/g.test(keyCode))
			{
				// 增加特殊Android键值处理，add 20151117
				if(keyCode === 'EVENT_OTT_KEY')
				{
					keyCode = arguments[3];// 最后一个额外参数表示的是原生安卓键值
					// 这里接收到的是Android键值，需要EPG转换成网页键值，也就是说映射工作交由EPG来维护
					// TODO 这里只是最简单的示例，正常键值无需管这个，特殊盒子特殊键值则需要自己去维护这套映射关系
					var androidKeyMap = 
					{
						3: KEY_HOME,
						8: KEY_BACK, // 返回键
						7: KEY_0,
						//8: KEY_1, // 数字1，因为8和KEY_BACK雷同，不作处理的话按1也会返回
						9: KEY_2,
						10: KEY_3,
						11: KEY_4,
						12: KEY_5,
						13: KEY_6,
						14: KEY_7,
						15: KEY_8,
						16: KEY_9,
						19: KEY_UP, // 上
						20: KEY_DOWN, // 下
						21: KEY_LEFT, // 左
						22: KEY_RIGHT,  // 右
						23: KEY_ENTER, // KEYCODE_DPAD_CENTER
						66: KEY_ENTER, // 小米盒子的KEYCODE_ENTER
						82: KEY_MENU // 菜单
					};
					keyCode = androidKeyMap[keyCode] || keyCode; //转换成真正的网页键值
				}
				
				if(typeof menuWindow === 'undefined' && Epg.eventHandler)
					Epg.eventHandler(keyCode);
				else if(typeof menuWindow !== 'undefined' && menuWindow.Epg.eventHandler)
					menuWindow.Epg.eventHandler(keyCode);
				return;
			}
			var event;
			if(document.createEvent)//如果是Chrome、Opera、Safari、Firefox
			{
				if(window.KeyEvent)//如果是Firefox
				{
					event = document.createEvent('KeyEvents');
					event.initKeyEvent(eventType, true, true, window, false, false, false, false, keyCode, 0);
				}
				else//如果是Chrome、Opera、Safari
				{
					event = document.createEvent('UIEvents');
					event.initUIEvent(eventType, true, true, window, 1);
					delete event.keyCode;
					if(typeof event.keyCode === "undefined")//如果是Chrome、Opera
						Object.defineProperty(event, "keyCode", {value:keyCode});
					else//如果是Safari
						event.key = String.fromCharCode(keyCode);
				}
				obj.dispatchEvent(event);
			}
			else if(document.createEventObject)//如果是IE
			{
				event = document.createEventObject();
				event.keyCode = keyCode;
				obj.fireEvent('on'+eventType, event);
			}
		},
		/**
		 * 绑定事件，add 20150603
		 * @param obj 事件对象，一般传document
		 * @param event 事件名，支持空格分隔多个事件
		 * @param fn 方法
		 */
		on: function(obj, event, fn)
		{
			var events = event.split(' ');
			for(var i in events)
				obj.addEventListener(events[i], fn, false);
		},
		/**
		 * 取消事件，add 20150603
		 * @param obj 事件对象，一般传document
		 * @param event 事件名，支持空格分隔多个事件
		 * @param fn 方法
		 */
		off: function(obj, event, fn)
		{
			var events = event.split(' ');
			for(var i in events)
				obj.removeEventListener(events[i], fn);
		},
		/**
		 * 临时绑定事件，执行一次后立即销毁，add 20150603
		 * @param obj 事件对象，一般传document
		 * @param event 事件名，支持空格分隔多个事件
		 * @param fn 方法
		 */
		one: function(obj, event, fn)
		{
			var tempFn = function()
			{
				fn();
				Epg.off(obj, event, tempFn);
			};
			Epg.on(obj, event, tempFn);
		},
		/**
		 * 使某张图片或者按钮闪烁，一个页面最多只能有一个按钮闪烁
		 */
		twinkle:
		{
			/**
			 * 开始闪烁，如果另一个按钮没有停止闪烁会强行停止
			 * @param id 图片或者某个按钮的ID
			 * @param time 多长时间闪烁一次，默认200毫秒
			 */
			start: function(id, time)
			{
				this.stop();//防止未停先起
				id = id || Epg.btn.current.id;
				time = (typeof time === 'number') ? time : 200;//默认200毫秒闪烁一次
				this._id = id;//之所以不直接用id是因为部分标清盒子要求这里的变量必须是全局的
				this._is_hide = false;//标记是否是隐藏的
				this._timer = setInterval(function()
				{
					if(Epg.twinkle._is_hide)
						S(Epg.twinkle._id);
					else
						H(Epg.twinkle._id);
					Epg.twinkle._is_hide = !Epg.twinkle._is_hide;//取反
				}, time);
			},
			/**
			 * 停止闪烁
			 */
			stop: function()
			{
				if(this._timer)
				{
					clearInterval(this._timer);
					this._timer = undefined;
					S(Epg.twinkle._id);//停止闪烁后恢复显示
				}
			}
		}
	});
	

	var curCSS;//模仿jQuery中读取css定义的对象，add 20140923
	if(window.getComputedStyle)//如果是谷歌或火狐
	{
		curCSS = function(elem, name) 
		{
			name = Epg.toHump(name,'-');//转驼峰形势
			var ret,
				computed = window.getComputedStyle(elem, null),
				style = elem.style;
			if(computed) 
				ret = computed[name];
			if(!ret)
				ret = style[name];
			return ret;
		};
	}
	else if(document.documentElement.currentStyle)//如果是IE
	{
		curCSS = function(elem, name)
		{
			name = Epg.toHump(name,'-');//转驼峰形势
			var ret = elem.currentStyle&&elem.currentStyle[name],
				style = elem.style;
			if(!ret&&style&&style[name])
			{
				ret=style[name];
			}
			return ret === '' ? 'auto' : ret;
		};
	}
	else //否则，如果是垃圾机顶盒
	{
		curCSS = function(elem,name)
		{
			name = Epg.toHump(name,'-');//转驼峰形势
			var style = elem.style;
			return style[name];
		};
	}

	/**
	 * 读取或修改元素的css属性，注意机顶盒支持的css很有限，请谨慎使用<br>
	 * 返回px的值时不会自动parseInt<br>
	 * 直接修改或者读取obj.style.xxx时不能读取定义在css中的样式
	 * @param obj 要读取或者设置的对象
	 * @param name css名称
	 * @param value 不传值代表获取，传值表示设置
	 * @returns
	 */
	Epg.css = function(obj, name, value)
	{
		if(value === undefined)//获取css属性
		{
			var temp = curCSS(obj, name);
			if(temp === '' || temp === 'auto') temp = 0;
			return temp;
		}
		else//设置css属性
		{
			var pxs = ['left','top','right','bottom','width','height','line-height','font-size'];
			var isPx = pxs.indexOf(name) >= 0; // 是否是带px的属性，这里简单处理
			if(isPx && !/.*px$/g.test(value + '') && value !== 'auto') value += 'px';
			obj.style[Epg.toHump(name)] = value;
		}
	};
	
	
	/**
	 * 与遥控器按键相关的方法，不影响旧版代码
	 */
	Epg.key = 
	{
		/**
		 * 所有与按键相关的方法都放在这里
		 */
		keys:
		{
			//KEY_5: function(){if(debug_mode)location.reload();}//如果是开发模式，按5刷新
		},
		ids: {},
		/**
		 * 逐个添加或者批量添加按键配置
		 */
		set: function(code, action)
		{
			if(typeof code === 'string' && action !== undefined)//如果是单个添加
			{
				//注意不能这样写：code={code:action}
				var _code = code;
				code = {};
				code[_code] = action;
			}
			if(typeof code === 'object')//批量添加
			{
				var obj = code;
				for(var i in obj)
				{
					//console.log("按键"+i+";;;;"+obj[i]);
					if(i.indexOf('KEY_') === 0 || i.indexOf('EVENT_') === 0){//如果是“KEY_”或者“EVENT_”开头，视作按键

						this.keys[i] = obj[i];
					}else{//否则，视作和按钮ID相关的方法
						this.ids[i] = obj[i];
					}
				}
			}
			else if(typeof code === 'number')//根本不允许出现这种错误！
			{
				alert('错误：添加按键映射时code不能为number类型！');
			}
			return this;
		},
		/** 和set方法一个意思 */
		add: function(code, action)
		{
			return this.set(code, action);
		},
		/**
		 * 逐个删除或者批量删除按键配置
		 */
		del: function(code)
		{
			if(!(code instanceof Array))
				code = [code];
			for(var i=0; i<code.length; i++)
			{
				if(this.ids[code[i]])
					this.ids[code[i]] = 'Epg.key.emptyFn()';
				if(this.keys[code[i]])
					this.keys[code[i]] = 'Epg.key.emptyFn()';//标清机顶盒delete有问题
			}
			return this;
		},
		/** 空方法，用于删除时 */
		emptyFn: function(){},
		/**
		 * 初始化eventHandler，随便什么时候调用、调用一次即可
		 */
		init: function()
		{
			if(!Epg.eventHandler)//避免重复定义
			{
				Epg.eventHandler = function(code)
				{
					for(var i in Epg.key.ids)//ID判断方法必须先执行，原因自己分析！
						if(Epg.Button.current.id === i)
							Epg.call(Epg.key.ids[i],code);
					for(var i in Epg.key.keys)
						if(code === window[i])
							Epg.call(Epg.key.keys[i],code);
				};
			}
		}
	};

	/**
	 * JS操作cookie工具类，add 20140529
	 */
	Epg.cookie=
	{
		/**
		 * 从js中获取cookie
		 * 由于标清机顶盒decodeURI有问题，所以获取cookie时不再自动URL解码
		 * 存cookie的时候，java代码里面存中文的话就URL编码一下，js获取时不做解码
		 * @param cookieName cookie名字
		 * @param defaultValue 默认值
		 * @param parseNumber 是否强转数字
		 * @param isUnescape 是否使用unescape来解码，注意，这个一般只用来解码“:/”等之类的简单符号，对于中文，整个机顶盒都甭想
		 * @returns
		 */
		get: function(cookieName, defaultValue, parseNumber, isUnescape)
		{
			var temp = new RegExp('(^|;| )'+cookieName+'=([^;]*)(;|$)', 'g').exec(document.cookie);
			if(temp!=null)
			{
				var value=temp[2];
				if(value === '""')//使用Java删除cookie时并不会立即删除，而是变成双引号，update 20150420
					return defaultValue;
				if(parseNumber == true)
					return parseFloat(value);
				if(isUnescape)//update 20150319，参数名和默认的unescape方法名重了，所以改为isUnescape
					return unescape(value);//URL解码，暂时用unescape代替，具体有没有问题有待日后观察
				return value;
			}
			return defaultValue;
		},
		/**
		 * 设置cookie
		 * @param name cookie名称
		 * @param value cookie内容，注意cookie内容不能有分号、逗号、等号、空格等特殊字符，中文就更不可以，所以注意使用escape
		 * @param day cookie失效天数，默认30天
		 * @param path cookie的作用范围，默认当前项目下
		 */
		set: function(name, value, day, path)
		{
			day = day==undefined?30:day;
			path = path==undefined?Epg.getBasePath():path;
			var str = name+'='+value+'; ';
			if(day)
			{
				var date = new Date();
				date.setTime(date.getTime()+day*24*3600*1000);
				str += 'expires='+date.toGMTString()+'; ';
			}
			if(path)
				str += 'path='+path;
			document.cookie = str;//注意，cookie这样设置并不会覆盖之前所有的cookie！除非同名同path
		},
		/**
		 * 删除cookie
		 * @param name cookie的名字
		 * @param path cookie所在的path，默认contextPath
		 */
		del: function(name, path)
		{
			this.set(name, null, -1, path);
		}
	};
	
	/**
	 * 滚动字幕方法，替换以前的marquee，解决crosswalk上滚动过快的问题，仅用于高清项目<br>
	 * 需要common_hd.css中增加相应的样式来配合使用
	 * add 20151201
	 */
	Epg.marquee =
	{
		/**
		 * 将div里面的某段静态文字变成滚动字幕，add 20151126
		 * @param maxLength 最长的文字个数，这里忽略英文、数字和中文之间的差别，统一按个数来算
		 * @param id div的ID，默认当前按钮ID+'_txt'
		 * @param amount 时间，这里含义其实是 速度(speed)，单位是px/s，即 像素每秒
		 * @param delay 延时，这里无意义
		 * @param dir 方向，默认left，这里无意义
		 * @param behavior 滚动方式，alternate为左右来回滚动，scroll为循环滚动，这里无意义
		 */
		start: function(maxLength, id, amount, delay, dir, behavior)
		{
			maxLength = maxLength || 7;
			id = id || Epg.Button.current.id+'_txt';
			amount = amount || 40; // 这里是速度的意思，px/s
			delay = delay || 10; // 无意义参数
			dir = dir || 'left'; // 无意义参数
			behavior = behavior || 'alternate';
			if(!this.rollId)
			{
				var marqueeParent = G(id);
				var html = Epg.trim(marqueeParent.innerHTML); // 去掉空格
				if(maxLength !== undefined && html.length > maxLength)
				{
					this.rollId = id; // 将ID存储起来
					this.innerHTML = html; // 将内容存储起来
					marqueeParent.innerHTML = '<div id="'+id+'_marquee" class="common_marquee">'+html+'</div>';
					var marqueeChild = G(id + '_marquee');
					var width1 = parseInt(Epg.css(marqueeParent, 'width'));
					var width2 = parseInt(Epg.css(marqueeChild, 'width'));
					var maxLeft = width2 - width1;
					console.log('maxLeft:' + maxLeft);
					if(maxLeft <= 0) return; // 如果字幕太短不足以滚动，直接返回不作处理
					var marquee_idx = Math.ceil(maxLeft / 50) * 50;//以50为梯度，计算合适的css
					marquee_idx = marquee_idx > 400 ? 400 : marquee_idx; // 最大400
					var time = (maxLeft / amount).toFixed(2);
					if(maxLeft < 30)
					{
						time = 0.7;
					}
					var animation = 'common_marquee_' + marquee_idx +' '+ time + 's linear 50ms infinite alternate';
					marqueeChild.style.webkitAnimation = animation;
					marqueeChild.style.animation = animation;
				}
			}
		},
		/**
		 * 停止滚动字幕
		 */
		stop: function()
		{
			if(this.rollId)
			{
				G(this.rollId).innerHTML = this.innerHTML;
				this.rollId = undefined;
			}
		}
	};
	
	/**
	 * 动画相关方法，add 20140923
	 */
	Epg.fx = 
	{
		interval: 13,//动画执行的帧率，意思是多少毫秒执行一次，默认13毫秒，jQuery默认就是这个值
		tagIdx: 0,//用来给timer递增取名字的索引
		animates: {},//存储所有执行过的动画数据，执行完毕后数据会被清空
		/**
		 * 开始一个动画，不兼容标清机顶盒，具体可能表现在：标清setInterval必须使用全局变量，标清的delete有问题，等
		 * @param obj 需要执行动画的对象
		 * @param params 动画参数，obj类型，形如：{left:'200px',top:'300px'}
		 * 				或者：{left:['100px','200px'],top:['50px','300px']}
		 * @param speed 速度，可以是number类型，表示毫秒数，也可以是字符串，如：'fast','normal','slow'
		 * @param easing 动画效果，默认swing
		 * @param callback 动画执行完毕后的回调函数
		 * @param tag 给动画取标签，如果开始一个动画前已经存在一个正在运行的、且标签相同的动画，
		 * 				那么这个动画会被强行停止，并直接变成动画结束时的状态，默认'default'
		 */
		start: function(obj, params, speed, easing, callback, tag)
		{
			var speeds = {fast:200, normal:400, slow:600};//预设的3个速度级别，同jQuery一模一样
			speed = (typeof speed === 'string' ? speeds[speed] : speed) || speeds.normal;
			if(typeof easing === 'function')
			{
				tag = callback;
				callback = easing;
				easing = '';
			}
			easing = easing || 'swing';
			tag = tag || 'default';//默认标签
			
			//遍历正在执行的动画集合，存在相同tag的动画时强行停止
			for(var i in this.animates)			
			{
				if(i.indexOf(tag)>=0)
					this.stop(i);//结束动画
			}
			
			var oldParams = params;//旧的params数据
			params = {};//新的params数据
			var canContinue = false;//标记动画是否能够继续执行下去
			for(var i in oldParams)//处理旧数据
			{
				var p = oldParams[i];
				if(!Epg.isArray(p)) //如果不是数组，强行变成数组
					p = [Epg.css(obj, i), p];//[当前CSS,目标CSS]
				else
					Epg.css(obj, i, p[0]);//p[0]代表起始状态，立即将元素变为起始状态，防止一闪而过的现象
				params[i] = {start: parseFloat(p[0]), end: parseFloat(p[1])};
				if(params[i].start !== params[i].end)
					canContinue = true;
			}
			if(!canContinue) return;//如果params中所有参数起始结束状态都一样，动画就没必要执行下去了
			tag += '_' + (++this.tagIdx);//最终的tag名字
			this.animates[tag] = {obj:obj, params:params, speed:speed, easing:easing, callback:callback, startTime:Date.now(), idx:0, timer:undefined};
			this.animates[tag].timer = setInterval(function()
			{
				var animate = Epg.fx.animates[tag];
				animate.idx++;//动画已经执行的次数，这个参数暂时没用
				var n = Date.now()-animate.startTime;//从开始当现在已经过去的时间
				if( n > animate.speed )
				{
					Epg.fx.stop(tag);//结束动画
					return;
				}
				var percent = n/animate.speed;//根据时间差计算百分比
				var pos = Epg.fx.easing[animate.easing](percent, n, 0, 1, animate.speed);
				for( var i in animate.params )
				{
					var p = animate.params[i];
					Epg.css(animate.obj, i, p.start + (p.end - p.start) * pos);
				}
			}, this.interval);
		},
		/**
		 * 停止某个正在运行的动画
		 * @param tag 保存在Epg.fx.animates中的标签名
		 * @return 是否成功，不过这个貌似不重要
		 */
		stop: function(tag)
		{
			var animate = Epg.fx.animates[tag];
			if(!animate)
				return false;
			clearInterval(animate.timer);//结束正在运行的动画
			var ps = animate.params;
			for(var i in ps)//让对象直接变成动画结束时的状态
				Epg.css(animate.obj, i, ps[i].end);
			Epg.call(animate.callback);//执行回调函数
			delete Epg.fx.animates[tag];//注意直接“delete animate”无效
			return true;
		},
		/**
		 * 模仿jQuery的easing函数，动画效果方法，返回值是当前时刻运动的百分比<br>
		 * 按照常规思路，动画的实现方式是这样的：<br>
		 * 通过setInterval每隔一定时间给某个值增加特定数值，直到这个值达到限制值。这样做的主要问题是，<br>
		 * 不同浏览器的运行速度不同，从而导致动画速度有差异，一般是IE下比较慢，Firefox下比较快。<br>
		 * 而jQuery.animate是以当前时间来决定位移值，某个时刻的位移值总是固定的，因而动画速度不会有差异。<br>
		 * 参考自：http://heeroluo.net/Article/Detail/67
		 */
		easing:
		{
			/**
			 * 匀速线性变化，其实这里有效参数只有一个，那就是p
			 * @param p 运动的时间，其实这里传的值是运动的百分比，即percent
			 * @param n 运动的次数，貌似真正含义是从运动开始到此刻的时间差
			 * @param firstNum 起始值，这里一般默认0
			 * @param diff 速度，这里默认1
			 * @returns
			 */
			linear: function( p, n, firstNum, diff )
			{
				return firstNum + diff * p;//等同于：return p;
			},
			swing: function( p, n, firstNum, diff )
			{
				return 0.5 - Math.cos( p * Math.PI ) / 2;
			}
		}
	};

	/**
	 * Epg.fx.start的快捷调用，add 20140923
	 */
	Epg.animate = function(obj, params, speed, easing, callback, tag)
	{
		Epg.fx.start(obj, params, speed, easing, callback, tag);
	};
	
})(Epg);








//==========================自动生成按钮移动方向代码相关===========================//
(function(Epg)
{
	var positions = {};//存放所有按钮的各种位置、大小等信息 

	/**
	 * 获取某个按钮某个方向上最合适的按钮
	 * @param cid 当前按钮的ID
	 * @param dir 方向
	 */
	function getBtn(cid, dir)
	{
		var current = positions[cid];
		var store = []; // 存放符合条件的临时按钮：{id,distance,deg}
		var leftOrRight = (dir === 'left' || dir === 'right');
		var hasFindDegIsZero = false; // 标记是否已经找到了deg为0的按钮
		for(var i in positions)
		{
			if(i === cid) continue; // 如果是自己，直接忽略
			var next = positions[i]; //下一个需要判断的目标按钮
			if( (dir === 'left' && next.left >= current.left)
				|| (dir === 'right' && next.right <= current.right)
				|| (dir === 'up' && next.top >= current.top)
				|| (dir === 'down' && next.bottom <= current.bottom) )
				continue; // 不在一个方向的按钮统统过滤

			if( leftOrRight && 
					 ( (next.centerY >= current.top && next.centerY <= current.bottom) // 目标按钮的Y中点在当前按钮的top和bottom之间
					|| (next.top <= current.centerY && next.bottom >= current.bottom) // 目标按钮的top在当前按钮的中线之上，bottom在当前按钮的bottom之下
					|| (next.top <= current.top && next.bottom >= current.centerY) ) // 目标按钮的top在当前按钮的top之上，bottom在当前按钮的中线之下
				)
			{
				store.push({id: i, distance: next.left, deg: 0}); // 把大致处于同一水平线的按钮的角度deg看成0
				hasFindDegIsZero = true;
			}
			else if( !leftOrRight && 
					 ( (next.centerX >= current.left && next.centerX <= current.right) 
					|| (next.left <= current.centerX && next.right >= current.right)
					|| (next.left <= current.left && next.right >= current.centerX) ) 
				)
			{
				store.push({id: i, distance: next.top, deg: 0});
				hasFindDegIsZero = true;
			}
			// 否则，计算剩下所有按钮与当前按钮的中点角度
			// 为了减少多余的计算，如果已经找到了deg为0的按钮就不需要再计算角度了
			else if(!hasFindDegIsZero)
			{
				var key1 = leftOrRight ? dir : 'centerX';
				var key2 = !leftOrRight ? (dir == 'up' ? 'top' : 'bottom') : 'centerY';
				var tan = (next[key1] - current[key1]) / (next[key2] - current[key2]);
				// 策略修改，原先是计算2个按钮的中点之间的角度，现在改成跟方向有关
				// 比如如果dir==down，那么计算2个按钮bottom的中点的角度，这样可以大大减少很多不需要的结果
				// var tan = (next.centerX - current.centerX) / (next.centerY - current.centerY);
				var cdeg = Math.abs(Math.atan(leftOrRight ? (1 / tan) : tan) * 360 / 2 / Math.PI);
				store.push({id: i, distance: next[leftOrRight?'left':'top'], deg: cdeg});
			}
		}
		var tidu = [0, 30, 45, 60, 85]; // 预设几个搜索梯度
		var _store = [];
		// 按梯度分别获取符合条件的按钮，去掉不合条件的
		for(var i=0; i<tidu.length; i++)
		{
			_store = [];
			for(var j=0; j<store.length; j++) if(store[j].deg <= tidu[i]) _store.push(store[j]);
			if(_store.length > 0) break;
		}
		store = _store.length ? _store : [];
		//对符合条件的按钮进行升序或者降序排序
		store.sort(function(a, b)
		{
			var desc = (dir === 'left' || dir === 'up') ? -1 : 1 ; // 默认升序，如果降序需要乘以-1
			return (a.distance < b.distance ? -1 : (a.distance > b.distance ? 1 : 0)) * desc;
		});
		// 此时绝大多数情况下，store[0].id就是我们要的结果，但是有时候会出现多个按钮distance一样的情况，
		// 以向右移动为例，可能2个目标按钮的left一样，此时我们取top离当前按钮top值最接近的那个按钮
		var nearest; // 最近的
		var result = '';
		var key = leftOrRight ? 'top' : 'left';
		for(var i=0; i<store.length; i++)
		{
			if(i>0 && store[i].distance != store[0].distance) break; // 不存在相同的情况直接跳出循环
			var distance = Math.abs(positions[store[i].id][key]-current[key]);//距离
			if(nearest === undefined || distance < nearest)
			{
				nearest = distance;
				result = store[i].id;
			}
		}
		return result;//没有找到符合条件的按钮时直接返回空字符串
	}

	/**
	 * 获取某一个元素相对于页面的绝对位置和大小，add 20150916
	 * @param elem
	 */
	Epg.getAbsolutePosition = function(elem)
	{
		if(elem == null) return {left: 0, top: 0, width: 0, height: 0, right: 0, bottom: 0};
		var left = elem.offsetLeft,
			top = elem.offsetTop,
			width = elem.offsetWidth,
			height = elem.offsetHeight;
		while(elem = elem.offsetParent)
		{
			left += elem.offsetLeft;
			top += elem.offsetTop;
		}
		return {left: left, top: top, width: width, height: height, right: left + width, bottom: top + height}; 
	};

	/**
	 * 给所有按钮自动生成上下左右移动对象，仅限高清项目使用，标清未作适配<br>
	 * 按钮不能用 display:none，因为这样获取不到位置信息，只能用 visibility:hidden<br>
	 * 如果某个按钮不想参与自动生成，配置“autoDirGroup: false”即可<br>
	 * 如果某个按钮的某个方向已经设置了，那么不会再重复为其自动生成<br>
	 * 如果某个按钮的某个方向既不想自己设置，也不想自动生成，直接设置“[]”空数组即可<br>
	 * 如果想将按钮进行分组，不同组别之间不能互相移动，可以针对不同分组的按钮设置不同的autoDirGroup<br>
	 * 比如，这3个按钮设置“autoDirGroup:1”， 那3个按钮设置“autoDirGroup:2”，不设置时默认值“default”<br>
	 * @start 20150916
	 * @last 20160309
	 * @param allButtons 按钮数组，就是一般EPG中定义的buttons
	 */
	Epg.autoMakeBtnDir = function(allButtons)
	{
		var start = new Date().getTime();
		var btnGroups = {};
		for(var i=0; i<allButtons.length; i++)
		{
			if(allButtons[i].autoDirGroup === false) continue;
			var gname = 'group_' + (allButtons[i].autoDirGroup === undefined ? 'default' : allButtons[i].autoDirGroup);
			btnGroups[gname] = btnGroups[gname] || [];
			btnGroups[gname].push(allButtons[i]);
		}
		for(var gname in btnGroups)
		{
			positions = {}; // 重置
			var btns = btnGroups[gname];
			for(var i=0; i<btns.length; i++)
			{
				if(btns[i].autoDir === false) continue;
				var id = btns[i].id;
				var obj = document.getElementById(id);
				var position = Epg.getAbsolutePosition(obj);
				position.obj = obj;
				position.centerX = position.left + position.width / 2;
				position.centerY = position.top + position.height / 2;
				positions[id] = position;
			}
			var dirs = ['left', 'right', 'up', 'down'];
			for(var i=0; i<btns.length; i++)
			{
				for(var j=0; j<dirs.length; j++)
				{
					btns[i][dirs[j]] = btns[i][dirs[j]] || getBtn(btns[i].id, dirs[j]); // 如果已经定义了就不要自动生成了
				}
			}
		}
		var end = new Date().getTime();
		console.log('自动生成按钮耗时：' + ( end - start ) + '毫秒！');
	};
})(Epg);


(function()
{
	/**
	 * 高清机顶盒滚屏方案实现，add 20160301
	 */
	Epg.scrollScreen = 
	{
		/**
		 * 最简单的调用只需：Epg.scrollScreen.init()即可，所有参数都是可选的
		 */
		init: function(option)
		{
			this.config.enable = true;
			for(var i in option)
			{
				if(option[i] !== undefined && option[i] !== "")
				{
					this.config[i] = option[i];
				}
			}
			// 进入一个页面的滚动初始值由URL参数“scrollTop”决定，如果没有，默认0
			this.scroll(Epg.getParamInt('scrollTop', 0));
		},
		/**
		 * 检测光标落到某个按钮之后是否需要滚动屏幕，这个一般无需用户手动调用
		 */
		check: function(button, dir)
		{
			if(!this.config.enable) return;
			//if(dir === 'left' || dir === 'right') return;
			if(button.restoreScroll)
			{
				this.scroll(-this.config.scrollTop);
				return;
			}
			if(!this.config.enableToAllBtn && !button.scrollScreen) return;
			var id = button.id;
			var position = document.getElementById(id).getBoundingClientRect();
			var target = document.getElementById(this.config.wrapperId);
			var safePx = this.config.safePx;
			/*var pageHeight = 720;
			if(position.bottom >= (pageHeight - safePx)) this.scroll(position.bottom - pageHeight + safePx);
			else if(position.top < this.config.topSafePx) this.scroll((this.config.topSafePx - position.top) * -1);*/
			
			var wrapperPosition = document.getElementById(this.config.wrapperId).getBoundingClientRect();
			if(position.bottom >= (wrapperPosition.bottom - safePx)) {
				this.scroll((position.bottom + safePx - wrapperPosition.bottom));
			}else if(position.top - wrapperPosition.top < safePx) {
				this.scroll((wrapperPosition.top - position.top + safePx) * -1);
			}
		},
		/**
		 * 如果开启了滚屏动画并且用户滚屏过快，由于动画还没有执行完导致没有滚动到指定位置，进而导致下一次获取光标位置出错，<br>
		 * 所以需要用本方法修复一下，如果没有滚动到指定位置，强制执行之！本方法针对开启了shareCursor时特别重要！
		 */
		repair: function()
		{
			if(!this.config.enable) return;
			var target = document.getElementById(this.config.wrapperId);
			if(Epg.scrollScreen.config.scrollTop !== target.scrollTop)
			{
				target.scrollTop = Epg.scrollScreen.config.scrollTop;
			}
		},
		/**
		 * 触发滚动，如果开启动画会有动画效果
		 * @param addScrollTop 滚动修改的值，比如往上滚动 20px 是 -20，往下滚动20px是 20
		 */
		scroll: function(addScrollTop)
		{
			var target = document.getElementById(this.config.wrapperId);
			var from = this.config.scrollTop;
			this.config.scrollTop += addScrollTop;
			if(enable_animate === false  || !this.config.animate || addScrollTop === 0){ target.scrollTop = this.config.scrollTop; return; }
			var rate = 13, speed = 600, start = Date.now(), easing = 'swing';
			if(this.config._interval){clearInterval(this.config._interval);this.config._interval = undefined;}
			this.config._interval = setInterval(function()
			{
				var n = Date.now() - start;
				if(n > speed)
				{
					clearInterval(Epg.scrollScreen.config._interval);
					Epg.scrollScreen.config._interval = undefined;
					n = speed;
				}
				var pos = Epg.fx.easing[easing](n/speed, n, 0, 1);
				target.scrollTop = from + pos * addScrollTop;
			}, rate);
		},
		/**
		 * 所有与滚屏相关的配置
		 */
		config:
		{
			animate: true, // 必须此值与全局的CONFG.ANIMATE都为true时才会有动画
			enable: false, // 全局参数，这个为false时所有代码失效
			enableToAllBtn: false, // 是否针对页面所有按钮开启滚屏检测，也就是说光标落到任何按钮上都会检测一下是否需要滚屏
			wrapperId: 'wrapper', // 最外一层容器
			scrollTop: 0, // 记录滚动的距离
			safePx: 40, // 安全像素，即距离屏幕边缘最大距离
			topSafePx: 40 // 上滚top安全像素
		}
	};
})();

(function() {
	Epg.scroll = {
		init: function(option) {
			this.config.enable = true;
			for(var i in option) {
				if(option[i] !== undefined && option[i] !== '') {
					this.config[i] = option[i];
				}
			}
			this.scr(Epg.getParamInt('scrollLeft', 0));
		},
		check: function(button, dir) {
			if(!this.config.enable) return;
			if(button.restoreScroll) {
				this.scr(-this.config.scrollLeft);
				return;
			}
			if(!this.config.enableToAllBtn && !button.scrollScreen) return;
			var id = button.id;
			var position = document.getElementById(id).getBoundingClientRect();
			var wrapperPosition = document.getElementById(this.config.wrapperId).getBoundingClientRect();
			var safePx = this.config.safePx;
			if(position.right >= (wrapperPosition.right - safePx)) {
				this.scr((position.right + safePx - wrapperPosition.right));			
			}else if(position.left - wrapperPosition.left < safePx) {
				this.scr((wrapperPosition.left - position.left + safePx) * -1);
			}
		},
		repair: function() {
			if(!this.config.enable) return;
			var target = document.getElementById(this.config.wrapperId);
			if(Epg.scroll.config.scrollLeft !== target.scrollLeft) {
				target.scrollLeft = Epg.scroll.config.scrollLeft;
			}
		},
		scr: function(addScrollLeft) {
			var target = document.getElementById(this.config.wrapperId);
			var form = this.config.scrollLeft;
			this.config.scrollLeft += addScrollLeft;
			if(enable_animate === false || !this.config.animate || addScrollLeft === 0) {target.scrollLeft = this.config.scrollLeft; return;}
			var rate = 13, speed = 600, start = Date.now(), easing = 'swing';
			if(this.config._interval) {clearInterval(this.config._interval); this.config._interval = undefined;}
			this.config._interval = setInterval(function() {
				var n = Date.now() - start;
				if(n > speed) {
					clearInterval(Epg.scroll.config._interval);
					Epg.scroll.config._interval = undefined;
					n = speed;
				}
				var pos = Epg.fx.easing[easing](n / speed, n, 0, 1);
				target.scrollLeft = form + pos * addScrollLeft;
			}, rate);
		},
		config: {
			animate: true,		// 此值与全局的CONFIG.ANIMATE都为true时才会有动画			
			enable: false,		// 全局参数，这个为false时所有代码失效
			enableToAllBtn: false,//是否针对页面所有按钮开启滚屏检测，也就是光标落到任何按钮上都会检测一下是否需要滚屏
			wrapperId: 'wrapper',// 最外层容器
			scrollLeft: 0,		// 记录滚动距离
			safePx: 40			// 安全像素，即距离屏幕边缘最大距离		
		}
	};
})();


/***************************正式上线代码到此结束***************************/


/**
 * 电脑上播放视频的方法，可以播放MP3也可以播放视频，不算正式代码<br>
 * 上线后可以删除，也可以保留，add 20150606
 */
(function(Epg)
{
	if(!debug_mode) return;//正式上线后这段代码失效
	//电脑上的MP对象
	var PCMP =
	{
		//必须在body之后调用本方法
		//这个defaultTip原本含义是是否显示默认提示
		//这里临时含义是：true表示播放视频，false播放MP3，不传默认视频
		init: function(defaultTip)
		{
			var type = defaultTip === false ? 'audio' : 'video';//注意 undefined 也默认为video
			if(!mp)
			{
				mp = document.createElement(type);
				mp.controls = false;//不显示浏览器自带控制条
				mp.autoplay = true;//自动播放
				document.body.insertBefore(mp, document.body.firstElementChild);
				
				//监听audio的播放结束事件，模拟EPG上面的EVENT_MEDIA_END
				mp.addEventListener('ended', function(e){Epg.fireKeyEvent((typeof menuWindow==='undefined')?document:menuWindow.document, 'keydown', 'EVENT_MEDIA_END');});
				mp.addEventListener('error', function(e){Epg.fireKeyEvent((typeof menuWindow==='undefined')?document:menuWindow.document, 'keydown', 'EVENT_MEDIA_ERROR');});
			}
		},
		
		/** 全屏播放 */
		fullscreenPlay: function(url)
		{
			if(arguments.length === 2)
				url = arguments[1];//如果传了2个参数，第二个参数才是视频地址，第一个是mp3地址，不管
			this.smallvodPlay(url, 0, 0, 1280, 720);//这里暂时不管标清项目
		},
		
		/** 小视频播放 */
		smallvodPlay: function(url, left, top, width, height)
		{
			mp.style.marginLeft = left + 'px';
			mp.style.marginTop = top + 'px';
			mp.style.width = width + 'px';
			mp.style.height = height + 'px';
			mp.src = url;
		},
		
		/** 从暂停、快进、快退中恢复 */
		resume: function(callback)
		{
			this.speed = 1;
			this.state = 'play';
			if(this.defaultTip) Epg.ykqTip("播放");
			mp.play();
			Epg.call(callback, [this]);
		},
		
		/** 定点播放 */
		playByTime: function(second)
		{
			mp.currentTime = second;
		},
		
		//获取当前播放时间，以前没有的方法
		getCurrentPlayTime: function()
		{
			if(mp) return Math.round(mp.currentTime || 0);
			else return 0;
		},
		
		//获取总时长，以前没有的方法
		getMediaDuration: function()
		{
			if(mp) return Math.round(mp.duration || 0);//还没加载时此值为NaN
			else return 0;
		},
		
		//获取当前声道
		getCurrentAudioChannel: function()
		{
			this.currentChannel = this.currentChannel || 'Stereo';
			return this.currentChannel;
		},
		
		/** 切换声道 */
		switchAudioChannel: function(callback)
		{
			this.currentChannel = this.getCurrentAudioChannel() === 'Stereo' ? 'Left' : 'Stereo';
			Epg.call(callback, [this.getCurrentAudioChannel(), this]);
			//mp.switchAudioChannel();
			//Epg.call(callback, [mp.getCurrentAudioChannel(), this]);
		},
		
		//销毁播放器
		destroy: function()
		{
			if(mp) mp.pause();//暂停可能会有一些问题，暂时不管了，反正不是正式代码
		}
	};
	
	//如果是电脑（简单判断），将播放器换成HTML5的Video标签，上线后debug_mode改为false这段代码就不会生效了
	if(navigator.userAgent.toLowerCase().indexOf('windows nt 6.3;')>=0)
	{
		for(var i in PCMP) Epg.Mp[i] = PCMP[i];
	}
})(Epg);

